package easyexcel

import (
	"errors"
	"gitee.com/hongzhaomin/hzm-common-go/toolkit"
	"github.com/xuri/excelize/v2"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"reflect"
	"slices"
	"time"
)

const (
	// 表头tag
	titleTag = "title"
	// 默认sheetName
	defaultSheetName = "Sheet1"
)

// excelize.File.SetColWidth(sheet, startCol, endCol, width)
type colWidthConfig struct {
	startCol string
	endCol   string
	width    float64
}

type easyExcel[T any] struct {
	beanType           reflect.Type      // 泛型反射类型（非指针）
	ptr                bool              // 泛型反射类型是否为指针
	fileName           string            // 文件名称
	sheetName          string            // excel表格sheet名称
	timeFormat         string            // 日期类型格式化
	tagTitles          []string          // 泛型结构体解析出来的表头切片
	titles             []string          // 实际excel的表头切片
	title2FieldNameMap map[string]string // 表头对应字段名称map
	picFields          []string          // 图片类型的字段名称
	timeFields         []string          // 时间类型的字段名称
	colWidthCnf        *colWidthConfig   // 列宽设置配置
	rowHeight          float64           // 行高设置配置
}

// 初始化 easyExcel
func (e *easyExcel[T]) initEasyExcel() {
	e.sheetName = defaultSheetName

	rt := reflect.TypeOf((*T)(nil)).Elem()
	if rt.Kind() == reflect.Ptr {
		rt = rt.Elem()
		e.ptr = true
	}
	if rt.Kind() != reflect.Struct {
		panic("must be struct type")
	}
	e.beanType = rt

	e.tagTitles = make([]string, 0)
	e.title2FieldNameMap = make(map[string]string)
	e.picFields = make([]string, 0)
	e.timeFields = make([]string, 0)
	for i := 0; i < rt.NumField(); i++ {
		field := rt.Field(i)
		if title, ok := field.Tag.Lookup(titleTag); ok {
			e.tagTitles = append(e.tagTitles, title)
			if _, ok := e.title2FieldNameMap[title]; ok {
				panic("excelTitle名称重复")
			}
			e.title2FieldNameMap[title] = field.Name

			// 图片类型的表头，暂时忽略掉，单独赋值
			rtField := field.Type
			if rtField.Kind() == reflect.Ptr {
				rtField = rtField.Elem()
			}
			if rtField.Kind() == reflect.Slice || rtField.Kind() == reflect.Array {
				if reflect.TypeOf(excelize.Picture{}) == rtField.Elem() {
					e.picFields = append(e.picFields, field.Name)
				}
			} else if rtField.Kind() == reflect.Struct && reflect.TypeOf(time.Time{}) == rtField {
				e.timeFields = append(e.timeFields, field.Name)
			}
		}
	}
	e.titles = make([]string, 0)
}

// bean转化为行数据
func (e *easyExcel[T]) settingFile(file *excelize.File) {
	// 设置列宽
	if e.colWidthCnf != nil {
		err := file.SetColWidth(e.sheetName, e.colWidthCnf.startCol, e.colWidthCnf.endCol, e.colWidthCnf.width)
		if err != nil {
			panic(err)
		}
	}
}

// 行数据转化为bean
func (e *easyExcel[T]) rows2Bean(file *excelize.File, rows []string, rowNum int) T {
	rt := e.beanType
	rvBean := reflect.New(rt)
	rv := reflect.Indirect(rvBean.Elem())
	for i, row := range rows {
		if i >= len(e.titles) {
			// 避免数组越界
			continue
		}
		fieldName, ok := e.title2FieldNameMap[e.titles[i]]
		if !ok {
			continue
		}

		colNum := i + 1
		//colName, err := excelize.ColumnNumberToName(i + 1) // 1 -> A
		//if err != nil {
		//	panic(err)
		//}
		cellName, err := excelize.CoordinatesToCellName(colNum, rowNum) // 1,1 -> A1
		if err != nil {
			panic(err)
		}

		structField, _ := rt.FieldByName(fieldName)
		rtField := structField.Type
		rvField := rv.FieldByName(fieldName)

		isPtr := rtField.Kind() == reflect.Ptr
		if isPtr {
			rtField = rtField.Elem()
		}
		switch rtField.Kind() {
		case reflect.Slice, reflect.Array:
			rtEle := rtField.Elem()
			if reflect.TypeOf(excelize.Picture{}) != rtEle {
				panic("不支持的数据类型")
			}
			pics, err := file.GetPictures(e.sheetName, cellName)
			if err != nil {
				panic(err)
			}
			if isPtr {
				rvField.Set(reflect.ValueOf(&pics))
			} else {
				rvField.Set(reflect.ValueOf(pics))
			}
		case reflect.Struct:
			if reflect.TypeOf(time.Time{}) != rtField {
				panic("不支持的数据类型")
			}
			timeFormat := e.timeFormat
			if timeFormat == "" {
				timeFormat = time.DateTime
			}
			date, err := time.ParseInLocation(timeFormat, row, time.Local)
			if err != nil {
				panic(err)
			}
			if isPtr {
				rvField.Set(reflect.ValueOf(&date))
			} else {
				rvField.Set(reflect.ValueOf(date))
			}
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
			reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
			reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
			val, err := toolkit.ReflectConvert4Str(rtField, row)
			if err != nil {
				panic(errors.New("类型转换异常" + err.Error()))
			}

			if isPtr {
				rvField.Set(val)
			} else {
				rvField.Set(val.Elem())
			}
		default:
			panic("excel data type not support.")
		}
	}
	if e.ptr {
		return rvBean.Interface().(T)
	}
	return rvBean.Elem().Interface().(T)
}

// bean转化为行数据
func (e *easyExcel[T]) bean2Rows(file *excelize.File, bean T, rowNum int) []any {
	rows := make([]any, len(e.tagTitles))
	rv := reflect.Indirect(reflect.ValueOf(bean))
	for i, tagTitle := range e.tagTitles {
		fieldName := e.title2FieldNameMap[tagTitle]
		rvField := rv.FieldByName(fieldName)
		// 设置行高
		if e.rowHeight > 0 {
			err := file.SetRowHeight(e.sheetName, rowNum, e.rowHeight)
			if err != nil {
				panic(err)
			}
		}

		// 图片特殊处理
		if slices.Contains(e.picFields, fieldName) {
			rows[i] = nil

			if field, ok := e.beanType.FieldByName(fieldName); ok {
				rtField := field.Type
				isPtr := rtField.Kind() == reflect.Ptr
				if isPtr {
					rtField = rtField.Elem()
				}
				if rtField.Kind() == reflect.Slice || rtField.Kind() == reflect.Array {
					rtEle := rtField.Elem()
					if reflect.TypeOf(excelize.Picture{}) == rtEle {
						pics := rvField.Interface()
						if pics == nil {
							continue
						}
						if isPtr {
							pics = rvField.Elem().Interface()
						}

						colNum := i + 1
						cellName, err := excelize.CoordinatesToCellName(colNum, rowNum) // 1,1 -> A1
						if err != nil {
							panic(err)
						}
						for _, pic := range pics.([]excelize.Picture) {
							if err := file.AddPictureFromBytes(e.sheetName, cellName, &pic); err != nil {
								panic(err)
							}
						}
					}
				}
			}
			continue
		}

		// 日期特殊处理
		if slices.Contains(e.timeFields, fieldName) && e.timeFormat != "" {
			// 存在时间类型的字段，且配置了时间格式化，特殊处理一下
			if field, ok := e.beanType.FieldByName(fieldName); ok {
				rtField := field.Type
				isPtr := rtField.Kind() == reflect.Ptr
				if isPtr {
					rtField = rtField.Elem()
				}
				if rtField.Kind() == reflect.Struct && reflect.TypeOf(time.Time{}) == rtField {
					val := rvField.Interface()
					if val == nil {
						rows[i] = nil
						continue
					}

					if isPtr {
						val = rvField.Elem().Interface()
					}
					rows[i] = val.(time.Time).Format(e.timeFormat)
					continue
				}
			}
		}
		rows[i] = rvField.Interface()
	}
	return rows
}

// 设置timeFormat
func (e *easyExcel[T]) setTimeFormat(timeFormat string) *easyExcel[T] {
	if timeFormat == "" {
		panic("time format 不能为空")
	}
	e.timeFormat = timeFormat
	return e
}

// 设置sheetName
func (e *easyExcel[T]) setSheetName(sheetName string) *easyExcel[T] {
	if sheetName == "" {
		panic("sheet name must not be blank")
	}
	e.sheetName = sheetName
	return e
}

// 列宽设置配置
func (e *easyExcel[T]) setColWidthConfig(startCol, endCol string, width float64) *easyExcel[T] {
	if startCol == "" {
		panic("startCol must not be blank")
	}
	if endCol == "" {
		panic("endCol must not be blank")
	}
	e.colWidthCnf = &colWidthConfig{
		startCol: startCol,
		endCol:   endCol,
		width:    width,
	}
	return e
}

// 行高设置配置
func (e *easyExcel[T]) setRowHeight(height float64) *easyExcel[T] {
	e.rowHeight = height
	return e
}
